/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "resize_load_cp.h"

#include <locale.h>
#include <stdbool.h>
#include <time.h>

#include "node.h"
#include "device_manager.h"
#include "hmfs_common.h"

namespace OHOS {
namespace Hmfs {
ResizeLoadCp::ResizeLoadCp(ResizeLoad *loadPtr) : load_(loadPtr)
{
	config_ = load_->GetConfig();
	sbInfo_ = load_->GetSbInfo();
}

uint32_t ResizeLoadCp::HmfsCheckpointChksum(struct CheckPointData *cpData)
{
    if (cpData == nullptr) {
        return EXIT_ERR_CODE;
    }
    unsigned int chksumOffset = LE32_TO_NATIVE(cpData->checksumOffset);
    uint32_t chksum = HmfsCommon::GetInstance().HmfsCalCrc32(HMFS_SUPER_MAGIC, cpData, chksumOffset);
    if (chksumOffset < CP_CHKSUM_OFFSET) {
        chksumOffset += sizeof(chksum);
        chksum = HmfsCommon::GetInstance().HmfsCalCrc32(chksum, (__u8 *)cpData + chksumOffset, HMFS_BLKSIZE - chksumOffset);
    }
    return chksum;
}

int32_t ResizeLoadCp::verifyCpchecksum(struct CheckPointData *cpData)
{
    if (cpData == nullptr) {
        return EXIT_ERR_CODE;
    }
    
    unsigned int chksumOffset = GetLeValue(cpData->checksumOffset);
    if (chksumOffset < CP_MIN_CHKSUM_OFFSET || chksumOffset > CP_CHKSUM_OFFSET) {
        HMFS_ERROR("\tInvalid CP CRC offset: %u", chksumOffset);
        return EXIT_ERR_CODE;
    }

    unsigned int crc = LE32_TO_NATIVE(*(uint32_t *)((unsigned char *)cpData + chksumOffset));
    unsigned int calCrc = HmfsCheckpointChksum(cpData);
    if (calCrc != crc) {
        HMFS_ERROR("\tInvalid CP CRC: offset:%u, crc:0x%x, calc:0x%x", chksumOffset, crc, calCrc);
        return EXIT_ERR_CODE;
    }
    return SUCCESS_CODE;
}

void* ResizeLoadCp::GetCheckpoint(uint32_t cpStartBlkId)
{
    void *checkPoint = malloc(HMFS_BLKSIZE);
    ASSERT(checkPoint);

    if (HmfsIo::GetInstance().DevReadBlock(checkPoint, cpStartBlkId) < 0) {
        ASSERT(0);
    }
    if (verifyCpchecksum((struct CheckPointData *)checkPoint) == -1) {
		HMFS_ERROR("verifyCpchecksum fail!");
		PrintCheckpointInfo((struct CheckPointData *)checkPoint, config_->debugLevel, config_->layout);
        free(checkPoint);
        return nullptr;
    }
    return checkPoint;
}

void* ResizeLoadCp::ValidateCheckpoint(uint32_t cpStartBlkId, unsigned long long *version)
{
    if (version == nullptr) {
        return nullptr;
    }
    /* Read the 1st cp block in this CP pack */
    void *checkPoint1 = GetCheckpoint(cpStartBlkId);
    if (!checkPoint1) {
        return nullptr;
    }
    struct CheckPointData *cpData = static_cast<CheckPointData *>(checkPoint1);
    if (GetLeValue(cpData->cpPackBlockCount) > sbInfo_->blocksPerSeg) {
        free(checkPoint1);
        return nullptr;
    }
    unsigned long long preVersion = GetLeValue(cpData->cpVersion);

    /* Read the 2nd cp block in this CP pack */
    cpStartBlkId += GetLeValue(cpData->cpPackBlockCount) - 1;
    void *checkPoint2 = GetCheckpoint(cpStartBlkId);
    if (!checkPoint2) {
        free(checkPoint1);
        return nullptr;
    }
    cpData = (struct CheckPointData *)checkPoint2;
    unsigned long long curVersion = GetLeValue(cpData->cpVersion);

    if (curVersion == preVersion) {
        *version = curVersion;
        free(checkPoint2);
        return checkPoint1;
    }
    free(checkPoint2);
    free(checkPoint1);
    return nullptr;
}

int32_t ResizeLoadCp::GetAvailCheckpoint()
{
    HMFS_DEBUG("Enter GetAvailCheckpoint");
    struct SuperBlockData *superB = sbInfo_->rawSuper;
    void *curCp;
    unsigned long blkSize = sbInfo_->blockSize;
    unsigned long long cp1Version = 0;
    unsigned long long cp2Version = 0;
    unsigned long long version;

    unsigned int cpPayload = GetLeValue(superB->cpPayload);
    if (cpPayload > HMFS_BLK_ALIGN(MAX_CP_PAYLOAD)) {
        return -EINVAL;
    }
    unsigned int cpBlks = 1 + cpPayload;
    sbInfo_->ckptData = static_cast<CheckPointData *>(malloc(cpBlks * blkSize));
    if (!sbInfo_->ckptData) {
        return -ENOMEM;
    }
    /*
     * Finding out valid cp block involves read both
     * sets( cp pack1 and cp pack 2)
     */
    unsigned long long cpStartBlkId = GetLeValue(superB->cpBlkId); 
    void *checkPoint1 = ValidateCheckpoint(cpStartBlkId, &cp1Version);

    /* The second checkpoint pack should start at the next segment */
    cpStartBlkId += 1 << GetLeValue(superB->logBlksPerSeg);
    void *checkPoint2 = ValidateCheckpoint(cpStartBlkId, &cp2Version);

    if (checkPoint1 && checkPoint2) {
        if (ver_after(cp2Version, cp1Version)) {
            curCp = checkPoint2;
            sbInfo_->curCpId = 2;
            version = cp2Version;
        } else {
            curCp = checkPoint1;
            sbInfo_->curCpId = 1;
            version = cp1Version;
        }
    } else if (checkPoint1) {
        curCp = checkPoint1;
        sbInfo_->curCpId = 1;
        version = cp1Version;
    } else if (checkPoint2) {
        curCp = checkPoint2;
        sbInfo_->curCpId = 2;
        version = cp2Version;
    } else {
        free(sbInfo_->ckptData);
        sbInfo_->ckptData = nullptr;
        return -EINVAL;
    }

    HMFS_INFO("CKPT version = %llx", version);

    memcpy(sbInfo_->ckptData, curCp, blkSize);

    if (cpBlks > 1) {
        unsigned long long cpBlkNo = GetLeValue(superB->cpBlkId);
        if (curCp == checkPoint2) {
            cpBlkNo += 1 << GetLeValue(superB->logBlksPerSeg);
        }

        /* copy sit bitmap */
        for (uint32_t i = 1; i < cpBlks; i++) {
            unsigned char *ckpt = (unsigned char *)sbInfo_->ckptData;
            int32_t ret = HmfsIo::GetInstance().DevReadBlock(curCp, cpBlkNo + i);
            ASSERT(ret >= 0);
            memcpy(ckpt + i * blkSize, curCp, blkSize);
        }
    }
    if (checkPoint1) {
        free(checkPoint1);
    }
    if (checkPoint2) {
        free(checkPoint2);
    }
    return SUCCESS_CODE;

}

int32_t ResizeLoadCp::HmfsCheckCheckPoint()
{
    struct SuperBlockData *sbData = sbInfo_->rawSuper;
    struct CheckPointData *cpData = sbInfo_->ckptData;
    unsigned int flag = GetLeValue(cpData->cpFlags);
    int i;
    uint32_t total = GetLeValue(sbData->segmentCount);
    uint32_t fsmeta = GetLeValue(sbData->segmentCountInCP);
    uint32_t sitSegments = GetLeValue(sbData->segmentCountInSIT);
    fsmeta += sitSegments;
    uint32_t natSegments = GetLeValue(sbData->segmentCountInNAT);
    fsmeta += natSegments;
    fsmeta += GetLeValue(cpData->rsvdSegmentCount);
    fsmeta += GetLeValue(sbData->segmentCountInSSA);

    if (fsmeta >= total) {
        return EXIT_ERR_CODE;
    }
    uint32_t ovpSegments = GetLeValue(cpData->overprovSegmentCount);
    uint32_t reservedSegments = GetLeValue(cpData->rsvdSegmentCount);

    if (!(GetLeValue(sbData->features) & NATIVE_TO_LE32(HMFS_FEATURE_RO)) &&
        (fsmeta < HMFS_MIN_SEGMENT || ovpSegments == 0 || reservedSegments == 0)) {
        HMFS_ERROR("\tWrong layout: check mkfs.hmfs version");
        return EXIT_ERR_CODE;
    }

    uint32_t userBlockCount = GetLeValue(cpData->userBlockCount);
    uint32_t segmentCountMain = GetLeValue(sbData->segmentCountInMain) + (NATIVE_TO_LE32(HMFS_FEATURE_RO) ? 1 : 0);
    uint32_t logBlocksPerSeg = GetLeValue(sbData->logBlksPerSeg);
    if (!userBlockCount || userBlockCount >= segmentCountMain << logBlocksPerSeg) {
        HMFS_ERROR("\tWrong userBlockCount(%u)", userBlockCount);

        if (load_->HmfsShouldProceed(sbData, flag) != EXIT_ERR_CODE) {
            return EXIT_ERR_CODE;
        }
        if (!config_->fixOn) {
            return EXIT_ERR_CODE;
        }
        if (flag & (CP_FSCK_FLAG | CP_RESIZEFS_FLAG)) {
            uint32_t seg_cnt_main = GetLeValue(sbData->segmentCount) - (GetLeValue(sbData->segmentCountInCP) +
                GetLeValue(sbData->segmentCountInSIT) + GetLeValue(sbData->segmentCountInNAT) +
                GetLeValue(sbData->segmentCountInSSA));

            /* validate segment_count_main in sb first */
            if (seg_cnt_main != GetLeValue(sbData->segmentCountInMain)) {
                HMFS_ERROR("Inconsistent segment_cnt_main %u in sb", segmentCountMain << logBlocksPerSeg);
                return EXIT_ERR_CODE;
            }
            uint32_t validUserBlocks = ((GetLeValue(sbData->segmentCountInMain) -
                GetLeValue(cpData->overprovSegmentCount)) * config_->blocksPerSegment);
            HMFS_INFO("Fix wrong userBlockCount in CP: (%u) -> (%u)", userBlockCount, validUserBlocks);
            SetLeValue(cpData->userBlockCount, validUserBlocks);
            config_->bugOn = 1;
        }
    }

    uint32_t mainSegmentss = GetLeValue(sbData->segmentCountInMain);
    uint32_t blocksPerSeg = sbInfo_->blocksPerSeg;
    for (i = 0; i < NR_CURSEG_NODE_TYPE; i++) {
        if (GetLeValue(cpData->curNodeSegNo[i]) >= mainSegmentss ||
            GetLeValue(cpData->curNodeBlkOffset[i]) >= blocksPerSeg)
            return EXIT_ERR_CODE;
    }
    for (i = 0; i < NR_CURSEG_DATA_TYPE; i++) {
        if (GetLeValue(cpData->curDataSegNo[i]) >= mainSegmentss ||
            GetLeValue(cpData->curDataBlkOffset[i]) >= blocksPerSeg)
            return EXIT_ERR_CODE;
    }

    uint32_t sitBitmapSize = GetLeValue(cpData->sitVersionBitmapSize);
    uint32_t natBitmapSize = GetLeValue(cpData->natVersionBitmapSize);

    if (sitBitmapSize != ((sitSegments / 2) << logBlocksPerSeg) / 8 ||
        natBitmapSize != ((natSegments / 2) << logBlocksPerSeg) / 8) {
        HMFS_ERROR("\tWrong bitmap size: sit(%u), nat(%u)", sitBitmapSize, natBitmapSize);
        return EXIT_ERR_CODE;
    }

    // cpStartSum = __start_sum_addr(sbInfo_);
    // cpPayload = __cp_payload(sbInfo_);
    uint32_t cpStartSum = LE32_TO_NATIVE(cpData->cpPackStartSum);
    uint32_t cpPayload = LE32_TO_NATIVE(sbData->cpPayload);
    if (cpStartSum < cpPayload + 1 || cpStartSum > blocksPerSeg - 1 - NR_CURSEG_TYPE) {
        HMFS_ERROR("\tWrong cpStartSum(%u) or cpPayload(%u)", cpStartSum, cpPayload);
        if ((GetLeValue(sbData->features) & HMFS_FEATURE_SB_CHKSUM)) {
            return EXIT_ERR_CODE;
        }
        SetLeValue(sbData->cpPayload, cpStartSum - 1);
        load_->UpdateSuperBlock(sbData, SB_MASK_ALL);
    }

    return SUCCESS_CODE;
}

int32_t ResizeLoadCp::HmfsLoadCpData()
{
    HMFS_DEBUG("Enter HmfsLoadCpData");
    int32_t ret = GetAvailCheckpoint();
    if (ret != SUCCESS_CODE) {
        HMFS_ERROR("Failed to find valid checkpoint");
        //dump_sbi_info(sbInfo_);
        PrintCheckpointInfo(F2FS_CKPT(sbInfo_), config_->debugLevel, config_->layout);
        return EXIT_ERR_CODE;
    }
    config_->bugOn = 0;
    if (HmfsCheckCheckPoint() != SUCCESS_CODE) {
        HMFS_ERROR("Failed to check checkpoint");
        //dump_sbi_info(sbInfo_);
        PrintCheckpointInfo(F2FS_CKPT(sbInfo_), config_->debugLevel, config_->layout);
        return EXIT_ERR_CODE;
    }

    PrintCheckpointInfo(F2FS_CKPT(sbInfo_), config_->debugLevel, config_->layout);

    if (config_->func != FSCK && config_->func != DUMP &&
        !IsCheckpointFlags(sbInfo_->ckptData, CP_UMOUNT_FLAG)) {
        HMFS_ERROR("Mount unclean image to replay log first");
        return EXIT_ERR_CODE;
    }

    return SUCCESS_CODE;
}

} // namespace Hmfs
} // namespace OHOS
